/*
 * Funambol is a mobile platform developed by Funambol, Inc. 
 * Copyright (C) 2008 Funambol, Inc.
 * 
 * This program is free software; you can redistribute it and/or modify it under
 * the terms of the GNU Affero General Public License version 3 as published by
 * the Free Software Foundation with the addition of the following permission 
 * added to Section 15 as permitted in Section 7(a): FOR ANY PART OF THE COVERED
 * WORK IN WHICH THE COPYRIGHT IS OWNED BY FUNAMBOL, FUNAMBOL DISCLAIMS THE 
 * WARRANTY OF NON INFRINGEMENT  OF THIRD PARTY RIGHTS.
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Affero General Public License 
 * along with this program; if not, see http://www.gnu.org/licenses or write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
 * MA 02110-1301 USA.
 * 
 * You can contact Funambol, Inc. headquarters at 643 Bair Island Road, Suite 
 * 305, Redwood City, CA 94063, USA, or at email address info@funambol.com.
 * 
 * The interactive user interfaces in modified source and object code versions
 * of this program must display Appropriate Legal Notices, as required under
 * Section 5 of the GNU Affero General Public License version 3.
 * 
 * In accordance with Section 7(b) of the GNU Affero General Public License
 * version 3, these Appropriate Legal Notices must retain the display of the
 * "Powered by Funambol" logo. If the display of the logo is not reasonably 
 * feasible for technical reasons, the Appropriate Legal Notices must display
 * the words "Powered by Funambol".
 */


#include "base/util/WKeyValuePair.h"

#include "AddressBookSyncSource.h"

#include <AddressBook/AddressBook.h>

#include "PersonDataConverter.h"

#include "base/util/WString.h"
#include "base/util/ArrayListEnumeration.h"
#include "base/util/KeyValuePair.h"

#include "client/BlockingSQLiteKeyValueStore.h"

#include "stringUtils.h"

#include <semaphore.h>

USE_NAMESPACE

void AddressBookSyncSource::error(const char* msg) {
    LOG.error(msg);
}

ABAddressBookRef AddressBookSyncSource::getAddressBook()
{
    if (!addressbook) {
        addressbook = ABAddressBookCreate();
        CFRetain(addressbook);
    }
    
    if (!addressbook) {
        error("opening address book");
    }
    
    return addressbook;
}


AddressBookSyncSource::AddressBookSyncSource(const char* name, AbstractSyncSourceConfig* sc)
: CacheSyncSource(name, sc, new BlockingSQLiteKeyValueStore(
        getTableName().c_str(), getKeyColumnName().c_str(), getValueColumnName().c_str(),
        getDatabasePath().c_str()
        )
    )
{
    addressbook = nil;
    pdc = [[PersonDataConverter alloc] init];
    sem_init(&sema,0,1);
}


AddressBookSyncSource::~AddressBookSyncSource()
{
    if (addressbook)
    {
        CFRelease(addressbook);
    }
    [pdc release];
    sem_destroy(&sema);
}

int AddressBookSyncSource::beginSync() {
    if (this->getSyncMode() == SYNC_REFRESH_FROM_SERVER)
    {
        //TODO : Move to CacheSyncSourcE?  Currently cache is not cleared
        return this->removeAllItems();
    }
    return 0;
}

void* AddressBookSyncSource::getItemContent(StringBuffer& key, size_t* size)
{    
    ABAddressBookRef ab = getAddressBook();
    ABRecordRef person = ABAddressBookGetPersonWithRecordID(ab, atoi(key.c_str()));
    
    //SyncItem sItem;
    StringBuffer vCard;
    [pdc personToVcard:person vcard:vCard];

    char * ret = stringdup(vCard.c_str());
    *size = strlen(ret);
    return (void*)ret;
}

Enumeration* AddressBookSyncSource::getAllItemList()
{
    ArrayListEnumeration * en = new ArrayListEnumeration;
    
    ABAddressBookRef ab = getAddressBook();
    CFArrayRef arr = ABAddressBookCopyArrayOfAllPeople(ab);
    
    int count = CFArrayGetCount(arr);
    
    for (int x = 0; x < count; x++)
    {
        ABRecordRef rec = CFArrayGetValueAtIndex(arr, x);
        CFRetain(rec);
        ABRecordID  key = ABRecordGetRecordID(rec);
        
        StringBuffer sb;
        sb.sprintf("%d", key);
        en->add(sb);
        
        CFRelease(rec);
    }
    
    CFRelease(arr);
    
    return en;
}   

int AddressBookSyncSource::insertItem(SyncItem& item)
{
    CFErrorRef err;
    
    ABAddressBookRef ab = getAddressBook();
    
    ABRecordRef abItem = ABPersonCreate();
    
    if (!abItem)
    {
        LOG.error("insertItem: Unable to create person");
        
        return STC_COMMAND_FAILED;
    }
    
    StringBuffer data((char*)item.getData());
    
    [pdc vCardToPerson:data person:abItem];
    
    lock();
    
    if (!ABAddressBookAddRecord(ab, abItem, &err))
    {
        LOG.error("insertItem: Unable to add Person to AddressBook for syncItem with key %s", item.getKey());
        CFRelease(abItem);
        // TODO : Handle error
        CFRelease(err);
        
        unlock();
        
        return STC_COMMAND_FAILED;
    }
    
    if (!ABAddressBookSave(ab, &err))
    {
        LOG.error("insertItem: Unable to save AddressBook for syncItem with key %s", item.getKey());
        CFRelease(abItem);
        // TODO : Handle error
        CFRelease(err);
        
        unlock();
        
        return STC_COMMAND_FAILED;
    }
    
    SInt32 uid = ABRecordGetRecordID(abItem);

    StringBuffer sb("");
    sb.sprintf("%d", uid);
    item.setKey(sb.c_str());
    
    CFRelease(abItem);
    
    LOG.info("insertItem: Success for item with key %s", item.getKey());
    
    unlock();
    
    return STC_ITEM_ADDED;
}   


int AddressBookSyncSource::modifyItem(SyncItem& item)
{
    CFErrorRef err;
    
    lock();
    
    ABAddressBookRef ab = getAddressBook();
    ABRecordRef abItem = ABAddressBookGetPersonWithRecordID(ab, atoi(item.getKey()));
    CFRetain(abItem);

    StringBuffer temp((char*)item.getData());

    [pdc vCardToPerson:temp person:abItem];

    if (!abItem)
    {
        LOG.error("modifyItem: Cannot get item with key %s", item.getKey());
        return STC_COMMAND_FAILED;
    }
    
    lock();

    if (!ABAddressBookSave(ab, &err))
    {
        LOG.error("modifyItem: Unable to save Person or AddressBook for syncItem with key %s", item.getKey());
        // TODO : Handle error
        CFRelease(abItem);
        CFRelease(err);
        
        unlock();
        
        return STC_COMMAND_FAILED;
    }
    
    SInt32 uid = ABRecordGetRecordID(abItem);

    StringBuffer sb("");
    sb.sprintf("%d", uid);
    item.setKey(sb.c_str());
    
    CFRelease(abItem);
    
    LOG.info("modifyItem: Success for item with key %s", item.getKey());
    
    unlock();
    
    return 200;
}   

int AddressBookSyncSource::removeItem(SyncItem& item)
{
    CFErrorRef err;
    
    ABAddressBookRef ab = getAddressBook();
    ABRecordRef abItem = ABAddressBookGetPersonWithRecordID(ab, atoi(item.getKey()));
    
    if (!abItem)
    {
        LOG.error("removeItem: Unable to get person to delete");
        
        return STC_ITEM_NOT_DELETED;
    }
    CFRetain(abItem);
    
    lock();
    
    if (!ABAddressBookRemoveRecord(ab, abItem, &err) || !ABAddressBookSave(ab, &err))
    {
        LOG.error("removeItem: Unable to remove Person or save AddressBook for syncItem with key %s", item.getKey());
        // TODO : Handle error
        CFRelease(err);
        CFRelease(abItem);
        
        unlock();
        
        return STC_ITEM_NOT_DELETED;
    }

    LOG.info("removeItem: Success for item with key %s", item.getKey());

    CFRelease(abItem);

    unlock();
    
    return 200;
}      

StringBuffer AddressBookSyncSource::getItemSignature(StringBuffer& key) {

    ABAddressBookRef ab = getAddressBook();
    int id;
    sscanf(key.c_str(), "%d", &id);
    //LOG.info("Getting signature of item with id: %d", id);
    ABRecordRef abItem = ABAddressBookGetPersonWithRecordID(ab, id);
    if (abItem)
    {
        CFRetain(abItem);
        StringBuffer ret = getLastModificationTime(abItem);
        CFRelease(abItem);
        return ret;
    }
    else
    {
        StringBuffer ret = "-1"; // Deleted?
        return ret;
    }
}

int AddressBookSyncSource::removeAllItems()
{
    lock();

    CFErrorRef err;
    
    ABAddressBookRef ab = getAddressBook();
    CFArrayRef arr = ABAddressBookCopyArrayOfAllPeople(ab);
    
    int count = CFArrayGetCount(arr);
    
    for (int x = 0; x < count; x++)
    {
        ABRecordRef rec = CFArrayGetValueAtIndex(arr, x);
        ABRecordID  key = ABRecordGetRecordID(rec);
        
        ABRecordRef abItem = ABAddressBookGetPersonWithRecordID(ab,key);
        if (!ABAddressBookRemoveRecord(ab, abItem, &err))
        {
            // TODO : Handle error
            LOG.error("removeAllItems: error removing record: %d", key);
            CFRelease(err);
            
            unlock();
            
            return 1;
        }
    }
    
    if (!ABAddressBookSave(ab, &err))
    {
        // TODO : Handle error
        CFRelease(err);
        
        unlock();
        
        return 1;
    }
    
    clearCache();
    
    setLastAnchor("");
    setNextAnchor("");

    unlock();
    
    return 0;
}

void AddressBookSyncSource::stopSync()
{
    endSync(false);
}


const StringBuffer AddressBookSyncSource::getLastModificationTime(ABRecordRef person)
{
    if (!person)
    {
        error("Cannot get last modified time of null person");
        return StringBuffer("0");
    }
    
    CFStringRef str = [pdc getPersonProperty:person property:kABPersonModificationDateProperty];

    StringBuffer ret = CFString2Std(str);
    CFRelease(str);
    return ret;
}


int AddressBookSyncSource::endSync(bool shouldUnlock)
{
    lock();
    
    saveCache();
    
    if (!shouldUnlock)
    {
        LOG.info("Will not unlock semaphore in endSync()");
        return 0;
    }

    unlock();
    
    // Wait for 1 second to make sure modification timestamps are before the anchor
    sleep(1000);
    
    return 0;
}




// Static

int AddressBookSyncSource::initializeDatabase()
{
    
    sqlite3 * db;
    int ret;
    
    ret = sqlite3_open(getDatabasePath().c_str(), &db);
    if (ret != SQLITE_OK)
    {
        setErrorF(ret, "Unable to open SQLite database: %s", getDatabasePath().c_str());
    }

    sqlite3_stmt * stmt;
    
    StringBuffer sql("");
    sql.sprintf("CREATE TABLE %s (%s TEXT PRIMARY KEY, %s TEXT)", getTableName().c_str(), getKeyColumnName().c_str(), getValueColumnName().c_str());
    
    ret = sqlite3_prepare(db, sql.c_str(), sql.length(), &stmt, NULL);
    if (ret != SQLITE_OK) {
        setErrorF(ret, "Unable to prepare create SQLite table in %s: %s", getDatabasePath().c_str(), getTableName().c_str());
        return ret;
    }
    ret = sqlite3_step(stmt);
    if (ret != SQLITE_OK && ret != SQLITE_DONE) {
        setErrorF(ret, "Unable to execute create SQLite table in %s: %s", getDatabasePath().c_str(), getTableName().c_str());
        return ret;
    }
    sqlite3_finalize(stmt);
    
    sqlite3_close(db);

    return 0;
}

/**
 * Verify the SQLite database used by the cache exists
 *
 * @return Zero on success, non-zero on failure
 **/
int AddressBookSyncSource::verifyDatabase()
{
    sqlite3 * db;
    int ret;
    ret = sqlite3_open(getDatabasePath().c_str(), &db);
    if (ret != SQLITE_OK)
    {
        setErrorF(ret, "Unable to open SQLite database: %s", getDatabasePath().c_str());
        return ret;
    }
    
    char ** temp;
    int rows;
    int cols;
    
    StringBuffer sql("");
    sql.sprintf("SELECT %s, %s FROM %s LIMIT 0", getKeyColumnName().c_str(), getValueColumnName().c_str(), getTableName().c_str());
    LOG.debug("Verify Database query: %s", sql.c_str());
    ret = sqlite3_get_table(db, sql.c_str(), &temp, &rows, &cols, NULL);
    if (ret != SQLITE_OK)
    {
        setErrorF(ret, "Unable to verify SQLite table in %s: %s", getDatabasePath().c_str(), getTableName().c_str());
    }
    sqlite3_close(db);
    
    return ret;
}


StringBuffer AddressBookSyncSource::getDatabasePath()
{
    StringBuffer sb(getHomeDir());
    //sb.append("/Documents");
    sb.append("/signatures.sql");
    LOG.debug("AddressBookSyncSource database setted in: %s", sb.c_str());
    return sb;
}

StringBuffer AddressBookSyncSource::getTableName()
{
    StringBuffer sb("addressbook");
    return sb;
}

StringBuffer AddressBookSyncSource::getValueColumnName()
{
    StringBuffer sb("lastModified");
    return sb;
}

StringBuffer AddressBookSyncSource::getKeyColumnName()
{
    StringBuffer sb("personID");
    return sb;
}


void AddressBookSyncSource::lock()
{
    sem_wait(&sema);
}

void AddressBookSyncSource::unlock()
{
    sem_post(&sema);
}